package com.cloudlead.common.persistence;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Date;

import com.cloudlead.common.lang.persistence.Filter;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.converters.SqlTimestampConverter;

import com.cloudlead.common.lang.persistence.Criteria;
import com.cloudlead.common.lang.persistence.Term;
import com.cloudlead.common.lang.persistence.IdEntity;
import com.cloudlead.common.lang.persistence.Operator;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.sql.RelationalPath;
import com.mysema.query.types.Path;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.expr.SimpleExpression;
import com.mysema.query.types.path.ComparablePath;
import com.mysema.query.types.path.PathBuilder;
import com.mysema.query.types.path.StringPath;

public class DynamicSpecifications {
    static {
        SqlTimestampConverter sqlTimestampConverter = new SqlTimestampConverter();
        sqlTimestampConverter.setPatterns(new String[]{"yyyy-MM-dd",
                "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss",
                "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss.S",
                "yyyy-MM-dd'T'HH:mm:ss.S"});
        ConvertUtils.register(sqlTimestampConverter, Timestamp.class);
        ConvertUtils.register(sqlTimestampConverter, Date.class);

    }

    public static <T> Specification<T> bySearchFilter(final Filter filter,
                                                      final Class<T> entityClass) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(RelationalPath<T> root,
                                         PathBuilder<T> builder,
                                         EntityPathResolver entityPathResolver) {
                BooleanBuilder booleanBuilder = new BooleanBuilder();
                if (null != filter) {
                    booleanBuilder = parseFilter(filter, root, builder,
                            entityPathResolver);
                }
                return booleanBuilder.getValue();
            }
        };
    }

    public static <T> Specification<T> bySearchCriteria(final Criteria criteria,
                                                      final Class<T> entityClass) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(RelationalPath<T> root,
                                         PathBuilder<T> builder,
                                         EntityPathResolver entityPathResolver) {
                BooleanBuilder booleanBuilder = new BooleanBuilder();
                if (null != criteria) {
                    booleanBuilder = parseCriteria(criteria, root, builder,
                            entityPathResolver);
                }
                return booleanBuilder.getValue();
            }
        };
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static <T> Specification<T> byExample(final IdEntity<String> entity,
                                                 final Class<T> entityClass) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(RelationalPath<T> root,
                                         PathBuilder<T> builder,
                                         EntityPathResolver entityPathResolver) {
                BooleanBuilder booleanBuilder = new BooleanBuilder();
                Class<? extends IdEntity> entityClass = entity.getClass();
                Field[] fields = entityClass.getDeclaredFields();
                for (Field field : fields) {
                    if (!Modifier.isFinal(field.getModifiers())
                            && !Modifier.isStatic(field.getModifiers())) {
                        Object value = null;
                        try {
                            field.setAccessible(true);
                            value = field.get(entity);
                        } catch (IllegalArgumentException
                                | IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        if (null != value) {
                            value = ConvertUtils
                                    .convert(value, field.getType());
                            Path<Object> property = entityPathResolver
                                    .getProperty(root, field.getName());
                            if (null != property
                                    && property instanceof SimpleExpression) {
                                SimpleExpression simpleExpression = (SimpleExpression) property;
                                booleanBuilder.and(simpleExpression.eq(value));
                            }

                        }
                    }

                }
                return booleanBuilder.getValue();
            }
        };
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private static <T> BooleanBuilder parseFilter(final Filter filter,
                                                  RelationalPath<T> root, PathBuilder<T> builder,
                                                  EntityPathResolver entityPathResolver) {
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        if (null != filter.getOr()) {
            for (Criteria or : filter.getOr()) {
                booleanBuilder.or(parseCriteria(or, root, builder,
                        entityPathResolver));
            }
        }
        return booleanBuilder;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private static <T> BooleanBuilder parseCriteria(final Criteria criteria,
                                                    RelationalPath<T> root, PathBuilder<T> builder,
                                                    EntityPathResolver entityPathResolver) {
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        for (Term filter : criteria.getAnd()) {
            Object value = filter.getValue();
            Path<T> expression = entityPathResolver.getProperty(root,
                    filter.getProperty());
            if (null == expression) {
                expression = (Path<T>) builder.get(filter.getProperty());
            }
            if (null == expression) {
                throw new IllegalArgumentException("filter property ["
                        + filter.getProperty() + "] not found.");
            }
            if (null == value) {// 空值处理
                if (expression instanceof SimpleExpression) {
                    SimpleExpression simpleExpression = (SimpleExpression) expression;
                    if (Operator.EQ.equals(filter.getOperator())) {
                        booleanBuilder.and(simpleExpression.isNull());
                    } else if (Operator.NE.equals(filter.getOperator())) {
                        booleanBuilder.and(simpleExpression.isNotNull());
                    } else {
                        throw new IllegalArgumentException("filter property ["
                                + filter.getProperty()
                                + "] condition value is empty.");

                    }
                }
            } else {
                // logic operator
                if (value instanceof String) {
                    value = ConvertUtils.convert((String) value,
                            expression.getType());
                }

                if (value instanceof Enum) {
                    Enum e = (Enum) value;
                    value = e.name();
                }

                if (expression instanceof SimpleExpression) {
                    SimpleExpression simpleExpression = (SimpleExpression) expression;
                    if (value instanceof Collection) {
                        Collection collection = (Collection) value;
                        switch (filter.getOperator()) {
                            case IN:
                                booleanBuilder.and(simpleExpression.in(collection));
                                break;
                            case NIN:
                                booleanBuilder.and(simpleExpression
                                        .notIn(collection));
                                break;
                            default:
                                throw new IllegalArgumentException(
                                        "filter property [" + filter.getProperty()
                                                + "] operator ["
                                                + filter.getOperator()
                                                + "] collection not support.");
                        }
                    } else if (value.getClass().isArray()) {
                        Object[] array = (Object[]) value;
                        switch (filter.getOperator()) {
                            case IN:
                                booleanBuilder.and(simpleExpression.in(array));
                                break;
                            case NIN:
                                booleanBuilder.and(simpleExpression.notIn(array));
                                break;
                            default:
                                throw new IllegalArgumentException(
                                        "filter property [" + filter.getProperty()
                                                + "] operator ["
                                                + filter.getOperator()
                                                + "] array not support.");
                        }
                    } else {

                        switch (filter.getOperator()) {
                            case EQ:
                                booleanBuilder.and(simpleExpression.eq(value));
                                break;
                            case NE:
                                booleanBuilder.and(simpleExpression.ne(value));
                                break;
                            case IN:
                                booleanBuilder.and(simpleExpression.in(value));
                                break;
                            case NIN:
                                booleanBuilder.and(simpleExpression.notIn(value));
                                break;
                            default:

                                if (value instanceof Comparable) {
                                    Comparable comparable = (Comparable) value;
                                    ComparablePath comparableExpression = new ComparablePath(
                                            expression.getType(), root, expression
                                            .getMetadata().getName());
                                    switch (filter.getOperator()) {
                                        case GT:
                                            booleanBuilder.and(comparableExpression
                                                    .gt(comparable));
                                            break;
                                        case LT:
                                            booleanBuilder.and(comparableExpression
                                                    .lt(comparable));
                                            break;
                                        case GE:
                                            booleanBuilder.and(comparableExpression
                                                    .goe(comparable));
                                            break;
                                        case LE:
                                            booleanBuilder.and(comparableExpression
                                                    .loe(comparable));
                                            break;
                                        default:
                                            if (expression instanceof StringPath) {
                                                StringPath stringPath = (StringPath) expression;
                                                switch (filter.getOperator()) {
                                                    case LK:
                                                        booleanBuilder.and(stringPath
                                                                .like("%" + value + "%"));
                                                        break;
                                                    case NLK:
                                                        booleanBuilder
                                                                .and(stringPath.notLike("%"
                                                                        + value + "%"));
                                                        break;
                                                    default:
                                                        throw new IllegalArgumentException(
                                                                "filter property ["
                                                                        + filter.getProperty()
                                                                        + "] operator ["
                                                                        + filter.getOperator()
                                                                        + "] not support.");
                                                }

                                            }
                                            break;
                                    }
                                }
                                break;
                        }
                    }
                } else {
                    throw new IllegalArgumentException("filter property ["
                            + filter.getProperty() + "] not simple expression.");
                }
            }
        }
        return booleanBuilder;
    }
}
